/***
 * Copyright (C) Microsoft. All rights reserved.
 * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
 *
 * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
 *
 * HTTP Library: JSON parser and writer
 *
 * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
 *
 * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 ****/

#include "pch.h"

#include <stdio.h>

#ifndef _WIN32
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
#endif

using namespace cljson;
using namespace clstring;
using namespace clstring::conversions;

//
// JSON Serialization
//

std::string cljson::w2a(const std::wstring& str)
{
    int nLen = WideCharToMultiByte(CP_ACP, 0, str.c_str(), -1, NULL, 0, NULL, NULL);
    char* pBuf = new char[nLen + 1];
    ZeroMemory(pBuf, nLen + 1);
    WideCharToMultiByte(CP_ACP, 0, str.c_str(), str.length(), pBuf, nLen, NULL, NULL);
    std::string retStr = pBuf;

    delete[] pBuf;
    pBuf = NULL;
    return retStr;
}

std::wstring cljson::a2w(const std::string& str)
{
    int nLen = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
    wchar_t* pwBuf = new wchar_t[nLen + 1];
    ZeroMemory(pwBuf, nLen * sizeof(wchar_t) + 2);
    MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nLen);
    std::wstring retStr = pwBuf;

    delete[] pwBuf;
    pwBuf = NULL;
    return retStr;
}

#ifdef _WIN32
void cljson::value::serialize(std::ostream& stream) const
{
    // This has better performance than writing directly to stream.
    std::string str;
    m_value->serialize_impl(str);
    stream << str;
}
void cljson::value::format(std::basic_string<wchar_t>& string) const { m_value->format(string); }
#endif

void cljson::value::serialize(clstring::ostream_t& stream) const
{
#ifndef _WIN32
    clstring::details::scoped_c_thread_locale locale;
#endif

    // This has better performance than writing directly to stream.
    clstring::string_t str;
    m_value->serialize_impl(str);
    stream << str;
}

void cljson::value::format(std::basic_string<char>& string) const { m_value->format(string); }

template<typename CharType>
void cljson::details::append_escape_string(std::basic_string<CharType>& str,
    const std::basic_string<CharType>& escaped)
{
    for (const auto& ch : escaped)
    {
        switch (ch)
        {
        case '\"':
            str += '\\';
            str += '\"';
            break;
        case '\\':
            str += '\\';
            str += '\\';
            break;
        case '\b':
            str += '\\';
            str += 'b';
            break;
        case '\f':
            str += '\\';
            str += 'f';
            break;
        case '\r':
            str += '\\';
            str += 'r';
            break;
        case '\n':
            str += '\\';
            str += 'n';
            break;
        case '\t':
            str += '\\';
            str += 't';
            break;
        default:

            // If a control character then must unicode escaped.
            if (ch >= 0 && ch <= 0x1F)
            {
                static const std::array<CharType, 16> intToHex = {
                    {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'} };
                str += '\\';
                str += 'u';
                str += '0';
                str += '0';
                str += intToHex[(ch & 0xF0) >> 4];
                str += intToHex[ch & 0x0F];
            }
            else
            {
                str += ch;
            }
        }
    }
}

void cljson::details::format_string(const clstring::string_t& key, clstring::string_t& str)
{
    str.push_back('"');
    append_escape_string(str, key);
    str.push_back('"');
}

#ifdef _WIN32
void cljson::details::format_string(const clstring::string_t& key, std::string& str)
{
    str.push_back('"');
    append_escape_string(str, clstring::conversions::to_utf8string(key));
    str.push_back('"');
}
#endif

void cljson::details::_String::format(std::basic_string<char>& str) const
{
    str.push_back('"');

    if (m_has_escape_char)
    {
        append_escape_string(str, clstring::conversions::to_utf8string(m_string));
    }
    else
    {
        str.append(clstring::conversions::to_utf8string(m_string));
    }

    str.push_back('"');
}

void cljson::details::_Number::format(std::basic_string<char>& stream) const
{
    if (m_number.m_type != number::type::double_type)
    {
        // #digits + 1 to avoid loss + 1 for the sign + 1 for null terminator.
        const size_t tempSize = std::numeric_limits<uint64_t>::digits10 + 3;
        char tempBuffer[tempSize];

#ifdef _WIN32
        // This can be improved performance-wise if we implement our own routine
        if (m_number.m_type == number::type::signed_type)
            _i64toa_s(m_number.m_intval, tempBuffer, tempSize, 10);
        else
            _ui64toa_s(m_number.m_uintval, tempBuffer, tempSize, 10);

        const auto numChars = strnlen_s(tempBuffer, tempSize);
#else
        int numChars;
        if (m_number.m_type == number::type::signed_type)
            numChars = snprintf(tempBuffer, tempSize, "%" PRId64, m_number.m_intval);
        else
            numChars = snprintf(tempBuffer, tempSize, "%" PRIu64, m_number.m_uintval);
#endif
        stream.append(tempBuffer, numChars);
    }
    else
    {
        // #digits + 2 to avoid loss + 1 for the sign + 1 for decimal point + 5 for exponent (e+xxx) + 1 for null
        // terminator
        const size_t tempSize = std::numeric_limits<double>::digits10 + 10;
        char tempBuffer[tempSize];
#ifdef _WIN32
        const auto numChars = _sprintf_s_l(tempBuffer,
            tempSize,
            "%.*g",
            clstring::details::scoped_c_thread_locale::c_locale(),
            std::numeric_limits<double>::digits10 + 2,
            m_number.m_value);
#else
        const auto numChars =
            snprintf(tempBuffer, tempSize, "%.*g", std::numeric_limits<double>::digits10 + 2, m_number.m_value);
#endif
        stream.append(tempBuffer, numChars);
    }
}

#ifdef _WIN32

void cljson::details::_String::format(std::basic_string<wchar_t>& str) const
{
    str.push_back(L'"');

    if (m_has_escape_char)
    {
        append_escape_string(str, m_string);
    }
    else
    {
        str.append(m_string);
    }

    str.push_back(L'"');
}

void cljson::details::_Number::format(std::basic_string<wchar_t>& stream) const
{
    if (m_number.m_type != number::type::double_type)
    {
        // #digits + 1 to avoid loss + 1 for the sign + 1 for null terminator.
        const size_t tempSize = std::numeric_limits<uint64_t>::digits10 + 3;
        wchar_t tempBuffer[tempSize];

        if (m_number.m_type == number::type::signed_type)
            _i64tow_s(m_number.m_intval, tempBuffer, tempSize, 10);
        else
            _ui64tow_s(m_number.m_uintval, tempBuffer, tempSize, 10);

        stream.append(tempBuffer, wcsnlen_s(tempBuffer, tempSize));
    }
    else
    {
        // #digits + 2 to avoid loss + 1 for the sign + 1 for decimal point + 5 for exponent (e+xxx) + 1 for null
        // terminator
        const size_t tempSize = std::numeric_limits<double>::digits10 + 10;
        wchar_t tempBuffer[tempSize];
        const int numChars = _swprintf_s_l(tempBuffer,
            tempSize,
            L"%.*g",
            clstring::details::scoped_c_thread_locale::c_locale(),
            std::numeric_limits<double>::digits10 + 2,
            m_number.m_value);
        stream.append(tempBuffer, numChars);
    }
}

#endif

const clstring::string_t& cljson::details::_String::as_string() const { 

    return m_string; 
}

const std::string& cljson::details::_String::as_string2() const {

    return w2a(m_string);
}

const clstring::string_t& cljson::value::as_string() const { 
    return m_value->as_string(); 
}

const std::string& cljson::value::as_string2() const {
    return m_value->as_string2();
}

clstring::string_t cljson::value::serialize() const
{
#ifndef _WIN32
    clstring::details::scoped_c_thread_locale locale;
#endif
    return m_value->to_string();
}

std::string cljson::value::serialize2(bool toUtf8) const
{
#ifndef _WIN32
    clstring::details::scoped_c_thread_locale locale;
#endif
    if (toUtf8)
    {
        return clstring::conversions::to_utf8string(m_value->to_string());
    }
    else
    {
        return w2a(m_value->to_string());
    }
}

std::string cljson::value::toBody() const
{
#ifndef _WIN32
    clstring::details::scoped_c_thread_locale locale;
#endif
    return clstring::conversions::to_utf8string(m_value->to_string());
}